# KeyValueRecordIdMapping.java

    package org.codefilarete.stalactite.engine.configurer.map;

    import java.util.HashMap;
    import java.util.Map;
    import java.util.function.Function;

    import org.codefilarete.stalactite.mapping.ComposedIdMapping;
    import org.codefilarete.stalactite.mapping.EmbeddedBeanMapping;
    import org.codefilarete.stalactite.mapping.IdAccessor;
    import org.codefilarete.stalactite.mapping.IdMapping;
    import org.codefilarete.stalactite.mapping.id.assembly.ComposedIdentifierAssembler;
    import org.codefilarete.stalactite.mapping.id.assembly.IdentifierAssembler;
    import org.codefilarete.stalactite.mapping.id.manager.AlreadyAssignedIdentifierManager;
    import org.codefilarete.stalactite.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.stalactite.sql.result.ColumnedRow;
    import org.codefilarete.stalactite.sql.result.Row;
    import org.codefilarete.tool.VisibleForTesting;
    import org.codefilarete.tool.collection.Maps;

    /**
     * {@link IdMapping} for {@link KeyValueRecord} : a composed id made of
     * - {@link KeyValueRecord#getId()}
     * - {@link KeyValueRecord#getKey()}
     */
    public class KeyValueRecordIdMapping<K, I, T extends Table<T>> extends ComposedIdMapping<KeyValueRecord<K, ?, I>, RecordId<K, I>> {

    	private KeyValueRecordIdMapping(
    			RecordIdAssembler recordIdAssembler) {
    		super(new KeyValueRecordIdAccessor<>(),
    				new AlreadyAssignedIdentifierManager<>((Class<RecordId<K, I>>) (Class) RecordId.class,
    						KeyValueRecord::markAsPersisted,
    						KeyValueRecord::isPersisted),
    				recordIdAssembler);
    	}

    	public <LEFTTABLE extends Table<LEFTTABLE>> KeyValueRecordIdMapping(
    			T targetTable,
    			Function<ColumnedRow, K> entryKeyAssembler,
    			Function<K, Map<Column<T, ?>, ?>> entryKeyColumnValueProvider,
    			IdentifierAssembler<I, LEFTTABLE> sourceIdentifierAssembler,
    			Map<Column<LEFTTABLE, ?>, Column<T, ?>> primaryKey2ForeignKeyMapping) {
    		this(new RecordIdAssembler<>(targetTable, entryKeyAssembler, entryKeyColumnValueProvider, sourceIdentifierAssembler, primaryKey2ForeignKeyMapping));
    	}

    	public <LEFTTABLE extends Table<LEFTTABLE>> KeyValueRecordIdMapping(
    			T targetTable,
    			EmbeddedBeanMapping<K, T> entryKeyMapping,
    			IdentifierAssembler<I, LEFTTABLE> sourceIdentifierAssembler,
    			Map<Column<LEFTTABLE, ?>, Column<T, ?>> primaryKey2ForeignKeyMapping) {
    		this(new RecordIdAssembler<>(targetTable, entryKeyMapping, sourceIdentifierAssembler, primaryKey2ForeignKeyMapping));
    	}

    	/**
    	 * Override because {@link ComposedIdMapping} is based on null identifier to determine newness, which is always false for {@link KeyValueRecord}
    	 * because they always have one. We delegate its computation to the entity.
    	 *
    	 * @param entity any non-null entity
    	 * @return true or false based on {@link KeyValueRecord#isNew()}
    	 */
    	@Override
    	public boolean isNew(KeyValueRecord<K, ?, I> entity) {
    		return entity.isNew();
    	}

    	public static class KeyValueRecordIdAccessor<K, I> implements IdAccessor<KeyValueRecord<K, ?, I>, RecordId<K, I>> {

    		@Override
    		public RecordId<K, I> getId(KeyValueRecord<K, ?, I> associationRecord) {
    			return associationRecord.getId();
    		}

    		@Override
    		public void setId(KeyValueRecord<K, ?, I> associationRecord, RecordId<K, I> identifier) {
    			associationRecord.setId(new RecordId<>(identifier.getId(), associationRecord.getKey()));
    			associationRecord.setKey(identifier.getKey());
    		}
    	}

    	/**
    	 * Identifier assembler for {@link RecordId}
    	 *
    	 * @param <KID> embedded key bean type
    	 * @param <SRCID> source identifier type
    	 */
    	@VisibleForTesting
    	static class RecordIdAssembler<KID, SRCID, T extends Table<T>> extends ComposedIdentifierAssembler<RecordId<KID, SRCID>, T> {

    		private final Function<ColumnedRow, KID> entryKeyAssembler;
    		private final Function<KID, Map<Column<T, ?>, ?>> entryKeyColumnValueProvider;
    		private final IdentifierAssembler<SRCID, ?> sourceIdentifierAssembler;
    		private final Map<Column<?, ?>, Column<T, ?>> primaryKey2ForeignKeyMapping;

    		/**
    		 * Constructor mapping given values to fields
    		 *
    		 * @param targetTable                  association table on which value should be inserted an read
    		 * @param entryKeyAssembler            builder of K instances from {@link Column} found in a {@link Row}
    		 * @param entryKeyColumnValueProvider  provides {@link Column} values to be inserted / updated from a K instance
    		 * @param sourceIdentifierAssembler    assemble / disassemble ID instances from / to {@link Column}s
    		 * @param primaryKey2ForeignKeyMapping used to remap {@link Column}s of sourceIdentifierAssembler to association table since it gives it for LEFTTABLE
    		 * @param <LEFTTABLE>                  table type of source declaring the relation
    		 */
    		@VisibleForTesting
    		<LEFTTABLE extends Table<LEFTTABLE>> RecordIdAssembler(
    				T targetTable,
    				Function<ColumnedRow, KID> entryKeyAssembler,
    				Function<KID, Map<Column<T, ?>, ?>> entryKeyColumnValueProvider,
    				IdentifierAssembler<SRCID, LEFTTABLE> sourceIdentifierAssembler,
    				Map<? extends Column<LEFTTABLE, ?>, ? extends Column<T, ?>> primaryKey2ForeignKeyMapping
    		) {
    			super(targetTable);
    			this.entryKeyAssembler = entryKeyAssembler;
    			this.entryKeyColumnValueProvider = entryKeyColumnValueProvider;
    			this.sourceIdentifierAssembler = sourceIdentifierAssembler;
    			this.primaryKey2ForeignKeyMapping = (Map) primaryKey2ForeignKeyMapping;
    		}

    		/**
    		 * Constructor for cases where K is a simple (embedded) bean.
    		 *
    		 * @param targetTable                  association table on which value should be inserted an read
    		 * @param entryKeyMapping              persistence mapping of K instance
    		 * @param sourceIdentifierAssembler    assemble / disassemble ID instances from / to {@link Column}s
    		 * @param primaryKey2ForeignKeyMapping used to remap {@link Column}s of sourceIdentifierAssembler to association table since it gives it for LEFTTABLE
    		 * @param <LEFTTABLE>                  table type of source declaring the relation
    		 */
    		@VisibleForTesting
    		<LEFTTABLE extends Table<LEFTTABLE>> RecordIdAssembler(
    				T targetTable,
    				EmbeddedBeanMapping<KID, T> entryKeyMapping,
    				IdentifierAssembler<SRCID, LEFTTABLE> sourceIdentifierAssembler,
    				Map<? extends Column<LEFTTABLE, ?>, ? extends Column<T, ?>> primaryKey2ForeignKeyMapping
    		) {
    			this(targetTable, entryKeyMapping::transform, entryKeyMapping::getInsertValues, sourceIdentifierAssembler, primaryKey2ForeignKeyMapping);
    		}

    		/**
    		 * Constructor for cases where entry key an entity referencing another table. Then K type becomes its identifier.
    		 *
    		 * @param targetTable                   association table on which value should be inserted an read
    		 * @param rightTableIdentifierAssembler entity identifier mapping (K type mapping)
    		 * @param sourceIdentifierAssembler     assemble / disassemble ID instances from / to {@link Column}s
    		 * @param primaryKey2ForeignKeyMapping  used to remap {@link Column}s of sourceIdentifierAssembler to association table since it gives it for LEFTTABLE
    		 * @param <LEFTTABLE>                   table type of source declaring the relation
    		 */
    		@VisibleForTesting
    		<LEFTTABLE extends Table<LEFTTABLE>, RIGHTTABLE extends Table<RIGHTTABLE>> RecordIdAssembler(
    				T targetTable,
    				IdentifierAssembler<KID, RIGHTTABLE> rightTableIdentifierAssembler,
    				IdentifierAssembler<SRCID, LEFTTABLE> sourceIdentifierAssembler,
    				Map<? extends Column<LEFTTABLE, ?>, ? extends Column<T, ?>> primaryKey2ForeignKeyMapping,
    				Map<Column<RIGHTTABLE, ?>, Column<T, ?>> rightTable2EntryKeyMapping
    		) {
    			this(targetTable, rightTableIdentifierAssembler::assemble, KID -> {
    				Map<Column<RIGHTTABLE, ?>, ?> keyColumnValues = rightTableIdentifierAssembler.getColumnValues(KID);
    				return Maps.innerJoin(rightTable2EntryKeyMapping, keyColumnValues);

    			}, sourceIdentifierAssembler, primaryKey2ForeignKeyMapping);
    		}

    		@Override
    		public RecordId<KID, SRCID> assemble(ColumnedRow row) {
    			SRCID SRCID = sourceIdentifierAssembler.assemble(row);
    			KID entryKey = entryKeyAssembler.apply(row);

    			return SRCID == null ? null : new RecordId<>(SRCID, entryKey);
    		}

    		@Override
    		public Map<Column<T, ?>, ?> getColumnValues(RecordId<KID, SRCID> record) {
    			Map<Column<?, ?>, Object> sourceColumnValues = (Map) sourceIdentifierAssembler.getColumnValues(record.getId());
    			Map<Column<T, ?>, Object> idColumnValues = Maps.innerJoin(primaryKey2ForeignKeyMapping, sourceColumnValues);
    			Map<Column<T, ?>, ?> entryKeyColumnValues = entryKeyColumnValueProvider.apply(record.getKey());
    			Map<Column<T, ?>, Object> result = new HashMap<>();
    			result.putAll(idColumnValues);
    			result.putAll(entryKeyColumnValues);
    			return result;
    		}
    	}
    }